 /* PvPSystem
 * Copyright (C) 2010 Spectre
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "PvPSystem.h"

const uint32 PvPRanks[MAX_PVP_RANKS+1][2] =
{
	{0, 0},
	{ALLIANCE_TITLE_PRIVATE,			HORDE_TITLE_SCOUT},
	{ALLIANCE_TITLE_CORPORAL,			HORDE_TITLE_GRUNT},
	{ALLIANCE_TITLE_SERGEANT,			HORDE_TITLE_SERGEANT},
	{ALLIANCE_TITLE_MASTER_SERGEANT,	HORDE_TITLE_SENIOR_SERGEANT},
	{ALLIANCE_TITLE_SERGEANT_MAJOR,		HORDE_TITLE_FIRST_SERGEANT},
	{ALLIANCE_TITLE_KNIGHT,				HORDE_TITLE_STONE_GUARD},
	{ALLIANCE_TITLE_KNIGHT_LT,			HORDE_TITLE_BLOOD_GUARD},
	{ALLIANCE_TITLE_KNIGHT_CPT,			HORDE_TITLE_LEGIONNAIRE},
	{ALLIANCE_TITLE_KNIGHT_CHAMP,		HORDE_TITLE_CENTURION},
	{ALLIANCE_TITLE_LT_COMMANDER,		HORDE_TITLE_CHAMPION},
	{ALLIANCE_TITLE_COMMANDER,			HORDE_TITLE_LT_GENERAL},
	{ALLIANCE_TITLE_MARSHAL,			HORDE_TITLE_GENERAL},
	{ALLIANCE_TITLE_FIELD_MARSHAL,		HORDE_TITLE_WARLORD},
	{ALLIANCE_TITLE_GRAND_MARSHAL,		HORDE_TITLE_HIGH_WARLORD}

};

//PvPManager
PvPManager::PvPManager()
{

}

PvPManager::~PvPManager()
{
	PvPRewardMap::iterator Itr;
	for(Itr = m_pvpRewards.begin(); Itr != m_pvpRewards.end(); ++Itr)
		delete Itr->second;
	m_pvpRewards.clear();
}

// Database Management
void PvPManager::LoadFromDatabase()
{
	PvPRewardMap::iterator Itr;
	for(Itr = m_pvpRewards.begin(); Itr != m_pvpRewards.end(); ++Itr)
		delete Itr->second;
	m_pvpRewards.clear();

	if(QueryResult_AutoPtr queryResult = WorldDatabase.Query("SELECT * FROM `trance_pvpsystem_rewards`"))
	{
		do
		{
			PvPSystemRewardData *rewardData = new PvPSystemRewardData(queryResult->Fetch());
			rewardData->Init();
			m_pvpRewards.insert(make_pair(rewardData->rewardRank, rewardData));
		}while(queryResult->NextRow());
	}
}

// Pvp Rewards
PvPSystemRewardData *PvPManager::GetPvPRewardData(uint8 rankId)
{
	PvPRewardMap::iterator Itr = m_pvpRewards.find(rankId);
	return Itr == m_pvpRewards.end() ? NULL : Itr->second;
}

// Hooks
void PvPManager::OnPlayerPvPKill(Player *pVictim, Player *pKiller)
{
	pKiller->GetPvPSystem()->OnPvPKill(pKiller, pVictim);
}

// PvPSystem
PvPSystem::PvPSystem(Player *pPlayer)
{
	m_plr = pPlayer;
	m_inRally = false;
	m_pvpData = NULL;
	m_shopDiscount = 0.00f;
	SetLastVendorListId(0);

	for(uint8 i = 0; i < MAX_TRANCE_SHOPS; i++)
		m_shopAccess[i] = 0;
}

PvPSystem::~PvPSystem()
{
	m_pvpTargets.clear();
}

// Database
void PvPSystem::LoadFromDatabase(Player *pPlayer)
{
	if(m_pvpData != NULL)
		return;

	if(QueryResult queryResult = WorldDatabase.PQuery("SELECT * FROM `trance_pvpsystem` where `playerGuid` = '%u'", pPlayer->GetGUIDLow()))
	{
		m_pvpData = new PvPSystemData(queryResult->Fetch());
		m_pvpData->Init();
	}
	else
	{
		m_pvpData = new PvPSystemData(NULL);
		m_pvpData->playerGuid	= pPlayer->GetGUIDLow();
		m_pvpData->playerRank	= 0;
		m_pvpData->totalKills	= 0;
		m_pvpData->nonFFAKills	= 0;
		m_pvpData->ffaKills		= 0;
		m_pvpData->totalDeaths	= 0;
		m_pvpData->totalExp		= 0;
	}
	_applyRankRewards();
}

void PvPSystem::SaveToDatabase()
{
	if(m_pvpData)
		WorldDatabase.PExecute("REPLACE INTO `trance_pvpsystem` VALUES ('%u', '%u', '%u', '%u', '%u', '%u', '%u')", m_pvpData->playerGuid, m_pvpData->playerRank, m_pvpData->totalKills, m_pvpData->nonFFAKills, m_pvpData->ffaKills, m_pvpData->totalDeaths, m_pvpData->totalExp);
}

// Stats Management
void PvPSystem::ModifyStat(uint8 statIndex, uint32 statValue)
{
	switch(statIndex)
	{
		case PVP_STAT_RANK:				m_pvpData->playerRank	+= statValue;	break;
		case PVP_STAT_TOTAL_KILLS:		m_pvpData->totalKills	+= statValue;	break;
		case PVP_STAT_NON_FFA_KILLS:	m_pvpData->nonFFAKills	+= statValue;	break;
		case PVP_STAT_FFA_KILLS:		m_pvpData->ffaKills		+= statValue;	break;
		case PVP_STAT_TOTAL_DEATHS:		m_pvpData->totalDeaths	+= statValue;	break;

		case PVP_STAT_TOTAL_EXP:
		{
			m_pvpData->totalExp += statValue;
			if(m_pvpData->totalExp >= GetNextRankExp())
				_awardPvPRank(m_pvpData->playerRank+1);
			else
				m_plr->BroadcastMessage("PvP-Exp Increased by: %u", statValue);
		}break;
	}
}

uint32 PvPSystem::GetStatValue(uint8 statIndex)
{
	switch(statIndex)
	{
		case PVP_STAT_RANK:
			return m_pvpData->playerRank;
		case PVP_STAT_TOTAL_KILLS:
			return m_pvpData->totalKills;
		case PVP_STAT_NON_FFA_KILLS:
			return m_pvpData->nonFFAKills;
		case PVP_STAT_FFA_KILLS:
			return m_pvpData->ffaKills;
		case PVP_STAT_TOTAL_DEATHS:
			return m_pvpData->totalDeaths;
		case PVP_STAT_TOTAL_EXP:
			return m_pvpData->totalExp;
		default:
			return 0;
	}
}

uint32 PvPSystem::GetNextRankExp()
{
	return PVP_EXP_MULTIPLIER * (GetStatValue(PVP_STAT_RANK)+1);
}

const char *PvPSystem::GetRankName()
{
	const CharTitlesEntry *charTitle = sCharTitlesStore.LookupEntry(PvPRanks[GetStatValue(PVP_STAT_RANK)][m_plr->GetTeamId()]);
	if(charTitle)
		return charTitle->name[sWorld.GetDefaultDbcLocale()];
	return "None";
}

// Rally Area
float PvPSystem::CalcDistance(WorldLocation aVect, WorldLocation bVect)
{
	float x = aVect.m_positionX - bVect.m_positionX;
	float y = aVect.m_positionY - bVect.m_positionY;
	float z = aVect.m_positionZ - bVect.m_positionZ;
	return sqrtf(z*z + y*y + x*x);

}

// PvP Interaction
void PvPSystem::OnPvPKill(Player *pAttacker, Player *pVictim)
{
	if(_isHonorable(pAttacker, pVictim) == false)
		return;

	uint32 currencyReward	= pVictim->pvpInfo.inFFAPvPArea ? PVP_REWARD_FFA : PVP_REWARD_NON_FFA;
	uint32 expReward		= pVictim->pvpInfo.inFFAPvPArea ? PVP_EXP_FFA_KILL : PVP_EXP_NON_FFA_KILL;
	if(pVictim->pvpInfo.inFFAPvPArea)
	{
		ModifyStat(PVP_STAT_FFA_KILLS, 1);
		pAttacker->BroadcastMessage("Free-For-All-Area Bonus Granted");
	}
	else
		ModifyStat(PVP_STAT_NON_FFA_KILLS, 1);

	uint32 attackerRank = GetStatValue(PVP_STAT_RANK);
	uint32 victimRank	= pVictim->GetPvPSystem()->GetStatValue(PVP_STAT_RANK);

	if(attackerRank > victimRank)
	{
		currencyReward	-= PVP_RANK_DIFF_COST*(attackerRank-victimRank);
		expReward		-= PVP_EXP_DIFF_COST*(attackerRank-victimRank);
		pAttacker->BroadcastMessage("Lower rank kill, points have been deducted");
	}

	if(WarZone *wZone = sWarZoneMgr.GetWarZoneBy(pAttacker))
	{
		currencyReward	+=	wZone->GetPvPCurrencyIncrease();
		expReward		+=	wZone->GetPvPExpIncrease();
	}
	// Stats Logging
	ModifyStat(PVP_STAT_TOTAL_KILLS, 1);

	// Victim
	pVictim->GetPvPSystem()->ModifyStat(PVP_STAT_TOTAL_DEATHS, 1);

	ModifyCurrency(pAttacker, _getCurrencyIncrease(currencyReward), false);
	ModifyStat(PVP_STAT_TOTAL_EXP, _getExpIncrease(expReward));
}

uint32 PvPSystem::GetTimeStamp()
{
	time_t UNIXTIME = time(NULL);
	tm LocalTime = *localtime(&UNIXTIME);
	return uint32(UNIXTIME);
}

// Internal PvP Checks
bool PvPSystem::_isHonorable(Player *pAttacker, Player *pVictim)
{
	if(!pVictim->GetSession() || !pAttacker->GetSession())
		return false;
	if(pVictim->HasAuraType(SPELL_AURA_NO_PVP_CREDIT) || pVictim->HasAura(SPELL_AURA_PLAYER_INACTIVE))
		return false;

	string aIp = pAttacker->GetSession()->GetRemoteAddress();
	string vIp = pVictim->GetSession()->GetRemoteAddress();
	if(aIp.compare(vIp) == 0 && pAttacker->isGameMaster() == false)
		return false;

	uint8 attackerRank	= GetStatValue(PVP_STAT_RANK);
	uint8 victimRank	= pVictim->GetPvPSystem()->GetStatValue(PVP_STAT_RANK);

	if(attackerRank > victimRank)
	{
		if(victimRank+MAX_PVP_RANK_DIFF < attackerRank)
		{
			pAttacker->BroadcastMessage("You cannot gain points from killer players %u ranks lower than your own", uint32(MAX_PVP_RANK_DIFF));
			return false;
		}
	}

	if(victimRank > attackerRank)
	{
		if(attackerRank+MAX_PVP_RANK_DIFF < victimRank)
		{
			pAttacker->BroadcastMessage("You cannot gain points from killer players %u ranks higher than your own", uint32(MAX_PVP_RANK_DIFF));
			return false;
		}
	}

	uint32 LastKilled = _getLastKilledTime(vIp);
	uint32 CurrentTime = GetTimeStamp();

	uint32 tMax = 180;
	uint32 Time = CurrentTime - LastKilled;
	uint32 tLeft = tMax - Time;
	if(tMax && tLeft < tMax)
	{
		pAttacker->BroadcastMessage("You may not collect points from %s for another %u seconds", pVictim->GetName(), tLeft);
		return false;
	}
	else
		_addTarget(vIp, CurrentTime);
	if(tMax)
		pAttacker->BroadcastMessage("You have killed %s you will not gain points from killing him again until another 3 minutes", pVictim->GetName());
	else
		pAttacker->BroadcastMessage("You have killed %s", pVictim->GetName());
	return true;
}

void PvPSystem::_addTarget(std::string Ip, uint32 Time)
{
	PvPTargetMap::iterator Itr = m_pvpTargets.find(Ip);
	if(Itr == m_pvpTargets.end())
		m_pvpTargets.insert(std::make_pair(Ip, Time));
	else
		Itr->second = Time;
}

uint32 PvPSystem::_getLastKilledTime(std::string Ip)
{
	PvPTargetMap::iterator Itr = m_pvpTargets.find(Ip);
	return Itr != m_pvpTargets.end() ? Itr->second : 0;
}

// Currency Management
void PvPSystem::ModifyCurrency(Player *pPlayer, int32 Amt, bool Remove)
{
	uint32 Current = pPlayer->GetMoney();
	char curMsg[200];
	if(Amt > 0 && Remove == false)
		pPlayer->ModifyMoney(Amt);
	else if(Amt <= Current && Remove)
		pPlayer->SetMoney(Current - Amt);
	else if (Remove)
		pPlayer->SetMoney(0);

	sprintf(curMsg, "Currency Modified %i %s %s", Amt/_getDivisorForAmt(Amt), _getCurrencyName(Amt).c_str(), Remove ? "Removed" : "Awarded");
	pPlayer->BroadcastMessage(curMsg);
}

uint16 PvPSystem::_getDivisorForAmt(uint32 Amt)
{
	float Res = Amt / 10000;
	if(Res >= 1.0f)
		return 10000;
	Res = Amt / 100;
	if(Res >= 1.0f)
		return 100;
	else return 1;
}

std::string PvPSystem::_getCurrencyName(uint32 Amt)
{
	uint16 Divisor = _getDivisorForAmt(Amt);
	string Blah;
	switch(Divisor)
	{
		case 10000:
		{
			Blah += "Gold";
		}break;

		case 100:
		{
			Blah += "Silver";
		}break;

		case 1:
		{
			Blah += "Copper";
		}break;

		default:
		{
			Blah += "Unknown";
		}break;
	}
	return Blah;
}

void PvPSystem::DisplayPvPRewards()
{
	m_plr->BroadcastMessage("Dispalying Current PvP-Rewards");
	m_plr->BroadcastMessage("------------------------------");
	m_plr->BroadcastMessage("PvP-Shop Access Level: %u", m_shopAccess[TRANCE_SHOP_PVP]);
	m_plr->BroadcastMessage("Title-Shop Access Level: %u", m_shopAccess[TRANCE_SHOP_TITLE]);
	m_plr->BroadcastMessage("Morph-Shop Access Level: %u", m_shopAccess[TRANCE_SHOP_MORPH]);
	m_plr->BroadcastMessage("Shop Discount: %.1f%%", m_shopDiscount);
	m_plr->BroadcastMessage("Currency Gain Increase: %.1f%%", m_currencyIncrease);
	m_plr->BroadcastMessage("Experience Gain Increase: %.1f%%", m_expIncrease);
}

// PvP Exp
void PvPSystem::_awardPvPRank(uint8 rankId)
{
	const CharTitlesEntry *charTitle = sCharTitlesStore.LookupEntry(PvPRanks[rankId][m_plr->GetTeamId()]);
	if(charTitle == NULL)
		return;

	m_pvpData->playerRank = rankId;
	m_pvpData->totalExp = 0;

	m_plr->SetTitle(charTitle);
	m_plr->SetUInt32Value(PLAYER_CHOSEN_TITLE, PvPRanks[rankId][m_plr->GetTeamId()]);

	m_plr->BroadcastMessage("You have achieved rank %s!", GetRankName());
	_applyRankRewards(rankId);
	SaveToDatabase();
}

void PvPSystem::_applyRankRewards(int8 rankId)
{
	PvPSystemRewardData *rewardData = sPvPManager.GetPvPRewardData(rankId == -1 ? GetStatValue(PVP_STAT_RANK) : rankId);
	if(rewardData == NULL)
		return;

	if(rankId != -1)
	{
		if(m_shopAccess[TRANCE_SHOP_PVP] < rewardData->rewardPvPShopLevel)
			m_plr->BroadcastMessage("Your PvP-Shop Access has been upgraded to level %u", rewardData->rewardPvPShopLevel);
		if(m_shopAccess[TRANCE_SHOP_TITLE] < rewardData->rewardTitleShopLevel)
			m_plr->BroadcastMessage("Your Title-Shop Access has been upgraded to level %u", rewardData->rewardTitleShopLevel);
		if(m_shopAccess[TRANCE_SHOP_MORPH] < rewardData->rewardMorphShopLevel)
			m_plr->BroadcastMessage("Your Morph-Shop Access has been upgraded to level %u", rewardData->rewardMorphShopLevel);
		if(m_shopDiscount < rewardData->rewardShopDiscount)
			m_plr->BroadcastMessage("Your Store Discount has been upgraded to a total of %.1f%%", rewardData->rewardShopDiscount);
		if(m_currencyIncrease < rewardData->rewardCurrencyIncrease)
			m_plr->BroadcastMessage("Your Currency Gain has been increased to a total of %.1f%%", rewardData->rewardCurrencyIncrease);
		if(m_expIncrease < rewardData->rewardExpIncrease)
			m_plr->BroadcastMessage("Your Experience Gain has been increased to a total of %.1f%%", rewardData->rewardExpIncrease);


	}

	m_shopAccess[TRANCE_SHOP_PVP]	= rewardData->rewardPvPShopLevel;
	m_shopAccess[TRANCE_SHOP_TITLE]	= rewardData->rewardTitleShopLevel;
	m_shopAccess[TRANCE_SHOP_MORPH]	= rewardData->rewardMorphShopLevel;

	m_shopDiscount		= rewardData->rewardShopDiscount;
	m_currencyIncrease	= rewardData->rewardCurrencyIncrease;
	m_expIncrease		= rewardData->rewardExpIncrease;

	if(rankId != -1)
	{
		ModifyCurrency(m_plr, rewardData->rewardCurrency, false);
		ModifyStat(PVP_STAT_TOTAL_EXP, rewardData->rewardExp);
	}
}

// Rewards
uint32 PvPSystem::GetShopDiscount(uint8 shopId, uint32 itemPrice)
{
	uint32 decreaseAmt = m_shopDiscount * itemPrice / 100;
	return itemPrice - decreaseAmt;
}

uint32 PvPSystem::_getCurrencyIncrease(uint32 defaultCurrency)
{
	uint32 increaseAmt = m_currencyIncrease * defaultCurrency / 100;
	return defaultCurrency + increaseAmt;
}

uint32 PvPSystem::_getExpIncrease(uint32 defaultExp)
{
	uint32 increaseAmt = m_expIncrease * defaultExp / 100;
	return defaultExp + increaseAmt;
}
